package org.joshsacks.fishies;

import org.joshsacks.fishies.GameBoard.MatchSet;
import org.joshsacks.fishies.GameBoard.MatchSet.Match;
import org.joshsacks.fishies.GameBoard.SlideDownSet;
import org.joshsacks.fishies.ui.gl.FishiesRenderer;
import org.joshsacks.fishies.ui.gl.GameBoardRenderer;

import android.content.Context;

public class FishiesController {
  private static final long ANIMATE_TIME = 20;

  private static final long MATCH_DURATION = 2000;

  public static final long SLIDE_DURATION = 300;
  private final Object syncObject = new Object();
  private final Score score;
  private FishiesRenderer renderer;
  private final GameBoard gameBoard;
  private SlideDownSet slideDownSet;
  private final Context context;
  private GamePiece currentPiece;
  private boolean suspended = true;
  private long startAnimateTime;
  private double animateFrom;
  private double animateTo;
  private long startRun;
  private boolean fallingFast;
  private long fastFallStart;
  private long fastFallMilli;
 
  private boolean rendered;
  
  private long animateStart;
  private MatchSet matchSet;
  
  public FishiesController(Context context) {
    score = new Score();
    this.context = context;
    gameBoard = new GameBoard();
  }

  public void setRendeded() {
    rendered = true;
  }
  
  public void setRenderer(FishiesRenderer renderer) {
    this.renderer = renderer;
  }

  public synchronized GameBoard getGameBoard() {
    return gameBoard;
  }
  
  public synchronized GamePiece getCurrentPiece() {
    return currentPiece;
  }

  public class ControllerThread implements Runnable {
    private static final long UPDATE_MILLIS = 10;
    public void run() {
      startRun = System.currentTimeMillis();
      while (!suspended) {
        try {
          Thread.sleep(UPDATE_MILLIS);
        } catch (InterruptedException e) {
        }
        synchronized (syncObject) {
          if (rendered) {
            animate();
          }
        }
      }    
    }
  }

  public void onResume() {
    suspended = false;
    rendered = false;
    new Thread(new ControllerThread()).start();
  }

  public void onPause() {
    suspended = true;
    rendered = false;
  }

  public boolean movePiece(boolean right) {  
    synchronized (syncObject) {
      if (currentPiece == null) {
        return false;
      }
      renderer.animateArrow(!right);

      double currentX = startAnimateTime > 0 ? animateTo : currentPiece.getX();   
      double nextX = currentX + GameBoardRenderer.getTileSize() * (right ? 1 : -1);
      if (nextX > GameBoardRenderer.getTileSize() * (GameBoard.WIDTH - 1)) {
        return false;
      } else if (nextX < 0) {
        return false;
      }
      int column = currentPiece.getColumn() + (right ? 1 : -1);
      int y = currentPiece.getBottomRow();
      if (gameBoard.getPosition(column, y) != null) {
        return false;
      }

      startAnimateTime = System.currentTimeMillis();
      animateFrom = currentPiece.getX();
      animateTo = nextX;
    }
    return true;  
  }

  public void rotatePiece() {
    if (currentPiece == null) {
      return;
    }
    synchronized(syncObject) {
      currentPiece.rotate();
      renderer.animateRotate();
    }
  }

  private void animate() {
    long fastFallTotal = fastFallMilli;
    if (fallingFast) {
      fastFallTotal += System.currentTimeMillis() - fastFallStart;
    }
    if (matchSet != null) {
      if (animateStart + MATCH_DURATION < System.currentTimeMillis()) {
        renderer.setMatchSet(null);
        score.addMatch(matchSet);
        matchSet = null;
        slideDownSet = gameBoard.getSlideSet();
        if (slideDownSet != null) {
          animateStart = System.currentTimeMillis();
          renderer.setSlideDownSet(slideDownSet);
        }
      }
      return;
    } else if (slideDownSet != null) {
      if (animateStart + SLIDE_DURATION < System.currentTimeMillis()) {
        gameBoard.slideDown(slideDownSet);
        slideDownSet = gameBoard.getSlideSet();
        if (slideDownSet != null) {
          animateStart = System.currentTimeMillis();
          renderer.setSlideDownSet(slideDownSet);
        } else {
          renderer.setSlideDownSet(null);
          checkMatch();
        }
      }
      return; 
    }
    
    if (currentPiece == null) {     
      startRun = System.currentTimeMillis();
      currentPiece = GamePiece.generatePiece(5);
      currentPiece.setY( 
        (GameBoard.HEIGHT + 3) * GameBoardRenderer.getTileSize() -
        (System.currentTimeMillis() - startRun) / 10.0f);        
      return;
    } 
    
    if (startAnimateTime > 0) {
      long timeDelta = System.currentTimeMillis() - startAnimateTime;
      double x;
      if (timeDelta > ANIMATE_TIME) {
        x = (double) animateTo;
        startAnimateTime = 0;
      } else {
        x = animateFrom + (animateTo - animateFrom) * 
            (timeDelta / (double) ANIMATE_TIME); 
      }
      currentPiece.setX(x);
    }
    long totalTime = (System.currentTimeMillis() - startRun);
    double oldY = currentPiece.getY();
    double y = 
      (GameBoard.HEIGHT + 3) * GameBoardRenderer.getTileSize() -
      (totalTime + fastFallTotal * 3) / 10.0f;
   
    int xColumn = currentPiece.getColumn();
    int yRow = currentPiece.getBottomRow();
    currentPiece.setY(y);
    int nextYRow = currentPiece.getBottomRow();
    if (nextYRow != yRow || y < GameBoardRenderer.getTileSize() * 3) {
      if (y < GameBoardRenderer.getTileSize() * 3
              || (gameBoard.getPosition(xColumn, nextYRow) != null)) {
        fallingFast = false;
        fastFallMilli = 0;
        if (yRow + 2 >= GameBoard.HEIGHT) {
          System.err.println("You lose: " + yRow);
          gameBoard.clear();
        } else {
          currentPiece.setY(oldY);               
          addPieceToBoard();
        }
        currentPiece = null;
      }
    }
  }

  public int getScore(Match match) {
    return score.getScoreForMatch(match);
  }
  
  private void addPieceToBoard() {
    gameBoard.addPiece(currentPiece);
    checkMatch();
  }

  private void checkMatch() {
    animateStart = System.currentTimeMillis();
    matchSet = gameBoard.findMatches();
    if (matchSet.isEmpty()) {
      score.addMatch(matchSet);
      matchSet = null;
      return;
    }
    renderer.setMatchSet(matchSet);    
    gameBoard.removeMatches(matchSet);
  }

  public Score getScore() {
    return score;
  }

  public Object getSync() {
    return syncObject;
  }

  public void fastFall(boolean fast) {
    renderer.highlightDownButton(fast);
    if (fallingFast == fast) {
      return;
    }
    System.err.println("FF: " + fast);
    fallingFast = fast;
    if (!fast) {
      fastFallMilli += System.currentTimeMillis() - fastFallStart;
    } else {
      fastFallStart = System.currentTimeMillis();
    }  
  }
}
